/*
 
(c) 2008 by Sławek Kruczek
slav.mir@gmail.com
 

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

package application;

import analytics.DefineableBinaryFunction;
import java.awt.Color;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.SpinnerNumberModel;
import javax.swing.ToolTipManager;
import javax.swing.filechooser.FileFilter;


public class MainGUI extends javax.swing.JFrame {
    
    public MainGUI() {
        // Otherwise the heavyweight component in the center obscures the tooltips
        ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false);
        
        initComponents(); 
        formulaChanged();
        
        
        // Quick and dirty PNG file filter
        saveFileChooser.setFileFilter(new FileFilter() {
            @Override
            public boolean accept(File f) {
                if(f.isDirectory()) return true;
                
                String name = f.getName();
                
                int i = name.lastIndexOf(".");
                if(i > 0 && i < name.length() - 1)
                    return name.substring(i + 1).toLowerCase().equals("png");
                else {
                    return false;
                }
            }

            @Override
            public String getDescription() {
                return "PNG images";
            }
        });
        
        
        // Progress bar updater
        functionCanvas.addProgressListener(new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                progressBar.setValue((Integer)evt.getNewValue());
            }
        });
        
        
    }
    
    
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        saveFileChooser = new javax.swing.JFileChooser();
        javax.swing.JToolBar toolBar = new javax.swing.JToolBar();
        javax.swing.JLabel formulaTextFieldLabel = new javax.swing.JLabel();
        formulaTextField = new javax.swing.JTextField();
        javax.swing.JToolBar.Separator jSeparator1 = new javax.swing.JToolBar.Separator();
        javax.swing.JLabel jLabel1 = new javax.swing.JLabel();
        detailLevelSpinner = new javax.swing.JSpinner();
        javax.swing.JToolBar.Separator jSeparator3 = new javax.swing.JToolBar.Separator();
        javax.swing.JLabel jLabel2 = new javax.swing.JLabel();
        rangeSpinner = new javax.swing.JSpinner();
        javax.swing.JToolBar.Separator jSeparator2 = new javax.swing.JToolBar.Separator();
        gridCheckBox = new javax.swing.JCheckBox();
        functionCanvas = new application.GLFunctionCanvas();
        progressBar = new javax.swing.JProgressBar();
        javax.swing.JPanel jPanel1 = new javax.swing.JPanel();
        saveSnapshotButton = new javax.swing.JButton();
        distanceSlider = new javax.swing.JSlider();

        saveFileChooser.setDialogTitle("Save image");
        saveFileChooser.setDialogType(javax.swing.JFileChooser.SAVE_DIALOG);

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("threeDee");
        setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
        setMinimumSize(new java.awt.Dimension(225, 225));

        toolBar.setFloatable(false);
        toolBar.setRollover(true);
        toolBar.setMargin(new java.awt.Insets(3, 3, 2, 3));

        formulaTextFieldLabel.setText("f(x, y) = ");
        toolBar.add(formulaTextFieldLabel);

        formulaTextField.setText("-sin(x * y / 9) * cos(y * -x / 9) + (x / 6)^2 + (y / 6)^2 - 1");
        formulaTextField.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                formulaTextFieldKeyPressed(evt);
            }
        });
        toolBar.add(formulaTextField);
        formulaTextField.getAccessibleContext().setAccessibleDescription("");

        toolBar.add(jSeparator1);

        jLabel1.setText("Detail level: ");
        toolBar.add(jLabel1);

        detailLevelSpinner.setModel(new javax.swing.SpinnerNumberModel(10, 1, 100, 1));
        detailLevelSpinner.setMinimumSize(new java.awt.Dimension(50, 20));
        detailLevelSpinner.setPreferredSize(new java.awt.Dimension(50, 20));
        detailLevelSpinner.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                detailLevelSpinnerStateChanged(evt);
            }
        });
        toolBar.add(detailLevelSpinner);
        toolBar.add(jSeparator3);

        jLabel2.setText("Range: ");
        toolBar.add(jLabel2);

        rangeSpinner.setModel(new javax.swing.SpinnerNumberModel(10, 1, 50, 1));
        rangeSpinner.setMinimumSize(new java.awt.Dimension(50, 20));
        rangeSpinner.setPreferredSize(new java.awt.Dimension(50, 20));
        rangeSpinner.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                rangeSpinnerStateChanged(evt);
            }
        });
        toolBar.add(rangeSpinner);
        toolBar.add(jSeparator2);

        gridCheckBox.setSelected(true);
        gridCheckBox.setText("Grid");
        gridCheckBox.setContentAreaFilled(false);
        gridCheckBox.setFocusPainted(false);
        gridCheckBox.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
        gridCheckBox.setHorizontalTextPosition(javax.swing.SwingConstants.RIGHT);
        gridCheckBox.setMargin(new java.awt.Insets(0, 0, 0, 0));
        gridCheckBox.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                gridCheckBoxStateChanged(evt);
            }
        });
        toolBar.add(gridCheckBox);

        getContentPane().add(toolBar, java.awt.BorderLayout.NORTH);

        functionCanvas.addMouseWheelListener(new java.awt.event.MouseWheelListener() {
            public void mouseWheelMoved(java.awt.event.MouseWheelEvent evt) {
                functionCanvasMouseWheelMoved(evt);
            }
        });
        functionCanvas.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                functionCanvasMousePressed(evt);
            }
        });
        functionCanvas.addMouseMotionListener(new java.awt.event.MouseMotionAdapter() {
            public void mouseDragged(java.awt.event.MouseEvent evt) {
                functionCanvasMouseDragged(evt);
            }
        });
        getContentPane().add(functionCanvas, java.awt.BorderLayout.CENTER);

        progressBar.setBorderPainted(false);
        progressBar.setDoubleBuffered(true);
        getContentPane().add(progressBar, java.awt.BorderLayout.SOUTH);

        jPanel1.setBackground(new java.awt.Color(30, 30, 30));
        jPanel1.setLayout(new java.awt.BorderLayout());

        saveSnapshotButton.setBackground(new java.awt.Color(50, 50, 50));
        saveSnapshotButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/application/resources/camera-photo.png"))); // NOI18N
        saveSnapshotButton.setToolTipText("Take a snapshot");
        saveSnapshotButton.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1));
        saveSnapshotButton.setContentAreaFilled(false);
        saveSnapshotButton.setFocusable(false);
        saveSnapshotButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        saveSnapshotButton.setMargin(new java.awt.Insets(2, 2, 2, 2));
        saveSnapshotButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        saveSnapshotButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                saveSnapshotButtonActionPerformed(evt);
            }
        });
        jPanel1.add(saveSnapshotButton, java.awt.BorderLayout.NORTH);

        distanceSlider.setBackground(new java.awt.Color(30, 30, 30));
        distanceSlider.setMaximum(30);
        distanceSlider.setOrientation(javax.swing.JSlider.VERTICAL);
        distanceSlider.setToolTipText("Viewing distance");
        distanceSlider.setValue(1);
        distanceSlider.setMaximumSize(new java.awt.Dimension(35, 32767));
        distanceSlider.setMinimumSize(new java.awt.Dimension(35, 36));
        distanceSlider.setPreferredSize(new java.awt.Dimension(30, 200));
        distanceSlider.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                distanceSliderStateChanged(evt);
            }
        });
        jPanel1.add(distanceSlider, java.awt.BorderLayout.CENTER);

        getContentPane().add(jPanel1, java.awt.BorderLayout.EAST);

        pack();
    }// </editor-fold>//GEN-END:initComponents

    
    
    private void detailLevelSpinnerStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_detailLevelSpinnerStateChanged
       functionCanvas.setDetail(((SpinnerNumberModel)detailLevelSpinner.getModel()).getNumber().intValue());
    }//GEN-LAST:event_detailLevelSpinnerStateChanged

    private void rangeSpinnerStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_rangeSpinnerStateChanged
        functionCanvas.setRange(((SpinnerNumberModel)rangeSpinner.getModel()).getNumber().intValue());
    }//GEN-LAST:event_rangeSpinnerStateChanged

    private void distanceSliderStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_distanceSliderStateChanged
        functionCanvas.setDistance(distanceSlider.getValue());
    }//GEN-LAST:event_distanceSliderStateChanged

    
    private Point dragPrevious;
    
    private void functionCanvasMouseDragged(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_functionCanvasMouseDragged
        Point dragPoint = evt.getPoint();
        
        int xOffset = dragPoint.x - dragPrevious.x;
        int yOffset = dragPoint.y - dragPrevious.y;
        
        dragPrevious = dragPoint;
        
        functionCanvas.setUpdating(false);
        if(evt.isShiftDown()) {
            functionCanvas.setHorizontalOffset(functionCanvas.getHorizontalOffset() + xOffset * .1f);
            functionCanvas.setVerticalOffset(functionCanvas.getVerticalOffset() - yOffset * .1f);
        } else {
            functionCanvas.setXyRotation(functionCanvas.getXyRotation() + yOffset);
            functionCanvas.setZRotation(functionCanvas.getZRotation() + xOffset);
        }
        functionCanvas.setUpdating(true);
        functionCanvas.display();
}//GEN-LAST:event_functionCanvasMouseDragged

    private void functionCanvasMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_functionCanvasMousePressed
        dragPrevious = evt.getPoint();
}//GEN-LAST:event_functionCanvasMousePressed

    private void functionCanvasMouseWheelMoved(java.awt.event.MouseWheelEvent evt) {//GEN-FIRST:event_functionCanvasMouseWheelMoved
        distanceSlider.setValue(distanceSlider.getValue() - evt.getWheelRotation());
}//GEN-LAST:event_functionCanvasMouseWheelMoved

    private void gridCheckBoxStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_gridCheckBoxStateChanged
        functionCanvas.setPaintingGrid(gridCheckBox.isSelected());
        functionCanvas.display();
    }//GEN-LAST:event_gridCheckBoxStateChanged

    private void formulaTextFieldKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_formulaTextFieldKeyPressed
        if(evt.getKeyCode() == KeyEvent.VK_ENTER) {
            formulaChanged();
            
        }
        else {
            formulaTextField.setBackground(new java.awt.Color(153, 204, 255));
            formulaTextField.setToolTipText("Press Enter to render the graph.");
        }
    }//GEN-LAST:event_formulaTextFieldKeyPressed

private void saveSnapshotButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveSnapshotButtonActionPerformed
    
    if(saveFileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
        BufferedImage image = functionCanvas.getSnapshot();

        File output = saveFileChooser.getSelectedFile();
        
        if(!output.getName().toLowerCase().endsWith(".png")) {
            output = new File(output.getAbsolutePath() + ".png");
        }

        if(output.exists()) {
            int result = JOptionPane.showConfirmDialog(this, "Do you want to overwrite " + output.getName() + "?", "Confirm overwrite", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
            
            if(result != 0)
                return;
        }
        
        try {
            javax.imageio.ImageIO.write(image, "png", output);
        }
        catch (IOException ex) { 
            JOptionPane.showMessageDialog(this, "Failed to save " + output.getName() + ".", "File save error", JOptionPane.ERROR_MESSAGE);
        }
    }
}//GEN-LAST:event_saveSnapshotButtonActionPerformed
    
    private void formulaChanged() {
        DefineableBinaryFunction f = null;
        
        try {
            f = new DefineableBinaryFunction(formulaTextField.getText());
            formulaTextField.setBackground(Color.WHITE);
            formulaTextField.setToolTipText("<html>" + 
                    "<p>Operators: <b>+</b>, <b>-</b>, <b>*</b>, <b>/</b>, <b>^</b>" +
                    "<p>Functions: <b>sin</b>, <b>asin</b>, <b>cos</b>, <b>acos</b>, <b>tan</b>, <b>atan</b>, <b>log</b>, <b>log10</b>, <b>neg</b>" +
                    "<p>Constants: <b>pi</b>, <b>e</b>");
        }
        catch(IllegalArgumentException e) {    
            formulaTextField.setBackground(Color.PINK);
            formulaTextField.setToolTipText(e.getMessage());
        }
        
        functionCanvas.setFunction(f);
    }
    
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new MainGUI().setVisible(true);
            }
        });
    }
    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JSpinner detailLevelSpinner;
    private javax.swing.JSlider distanceSlider;
    private javax.swing.JTextField formulaTextField;
    private application.GLFunctionCanvas functionCanvas;
    private javax.swing.JCheckBox gridCheckBox;
    private javax.swing.JProgressBar progressBar;
    private javax.swing.JSpinner rangeSpinner;
    private javax.swing.JFileChooser saveFileChooser;
    private javax.swing.JButton saveSnapshotButton;
    // End of variables declaration//GEN-END:variables
    
}
